#include <stdio.h>
#include <stdlib.h>
#include <errno.h>

static char *mem_start_brk; /* points to first byte of the heap */
static char *mem_brk;       /* points to last byte of the heap */
static char *mem_max_addr;  /* max virtual address for the heap */

/* mem_init - initializes the memory system model */
void mem_init (int size)
{
    mem_start_brk = (char *)malloc(size);   /* models available VM*/
    if (mem_start_brk == NULL)              
    {
        printf("malloc error\n");
        exit(1);
    }

    mem_brk = mem_start_brk;                /* heap is initially empty */
    mem_max_addr = mem_start_brk + size;    /* max virtual address for the heap */
}

/* mem_sbrk - simple model of the sbrk function. Extends the heap by incr
 * bytes and returns the start address of the new area. In this model, the
 * heap cannot be shrunk. */
void *mem_sbrk(int incr)
{
    char *old_brk = mem_brk;

    if ((incr < 0) || ((mem_brk + incr) > mem_max_addr))
    {
        errno = ENOMEM;
        return (void *)-1;
    }

    mem_brk += incr;
    return old_brk;
}

/* Basic constants and macros */
#define WSIZE       4       /* word size (bytes) */
#define DSIZE       8       /* doubleword size (sizes) */
#define CHUNKSIZE   (1<<12) /* initial heap size (bytes) */
#define OVERHEAD    8       /* overhead of header and footer(bytes) */

#define MAX(x, y) ((x) > (y)? (x) : (y))

/* Pack a size and allocated bit into a word */
#define PACK(size, alloc)   ((size) | (alloc))

/* Read and write a word at address p */
#define GET(p)              (*(size_t *)p)
#define PUT(p, val)         (*(size_t *)(p) = (val))

/* Read the size and allocated fields from address */
#define GET_SIZE(p)         (GET(p) & ~0x7)
#define GET_ALLOC(p)        (GET(p) & 0x1)

/* Given block ptr bp, compute address of its header and footer */
#define HDRP(bp)            ((char *)(bp) - WSIZE)
#define FTRP(bp)            ((char *)(bp) + GET_SIZE(HDRP(bp)) - DSIZE)

/* Given block ptr bp, compute address of next and previous blocks */
#define NEXT_BLKP(bp)           ((char *)(bp) + GET_SIZE(((char *)(bp) - WSIZE)))
#define PREV_BLKP(bp)           ((char *)(bp) - GET_SIZE(((char *)(bp) - DSIZE)))

static char *heap_listp;

static void *coalesce(void *bp)
{
    size_t prev_alloc = GET_ALLOC(FTRP(PREV_BLKP(bp)));
    size_t next_alloc = GET_ALLOC(HDRP(NEXT_BLKP(bp)));
    size_t size = GET_SIZE(HDRP(bp));

    if (prev_alloc && next_alloc)
    {
        return bp;
    }

    else if (prev_alloc && !next_alloc)
    {
        size += GET_SIZE(HDRP(NEXT_BLKP(bp)));
        PUT(HDRP(bp), PACK(size, 0));
        PUT(FTRP(NEXT_BLKP(bp)), PACK(size, 0));
        return bp;
    }

    else if (!prev_alloc && next_alloc)
    {
        size += GET_SIZE(HDRP(PREV_BLKP(bp)));
        PUT(HDRP(PREV_BLKP(bp)), PACK(size, 0));
        PUT(FTRP(bp), PACK(size, 0));
        return PREV_BLKP(bp);
    }   

    else 
    {
        size += GET_SIZE(HDRP(PREV_BLKP(bp))) +
            GET_SIZE(FTRP(NEXT_BLKP(bp)));
        PUT(HDRP(PREV_BLKP(bp)), PACK(size, 0));
        PUT(FTRP(NEXT_BLKP(bp)), PACK(size, 0));
        return PREV_BLKP(bp);
    }
}

static void *extend_heap(size_t words)
{
    char *bp;
    size_t size;

    /* Allocate an even number of words to maintain alignment */
    size = (words % 2) ? (words+1)*WSIZE : words*WSIZE;
    if ((int)(bp = mem_sbrk(size)) < 0)
        return NULL;

    /* initialize free block header/footer and epilogue header */
    PUT(HDRP(bp), PACK(size, 0));               /* free block header */
    PUT(FTRP(bp), PACK(size, 0));               /* free block footer */
    PUT(HDRP(NEXT_BLKP(bp)), PACK(0, 1));       /* new epilogue header */

    /* Coalesce if the previous block was free */
    return coalesce(bp);
}

int mm_init(void)
{
    /* create the initial empt heap */
    if ((heap_listp = mem_sbrk(4 * WSIZE)) == NULL)
        return -1;
    PUT(heap_listp, 0);                         /* alignment padding */
    PUT(heap_listp+WSIZE, PACK(OVERHEAD, 1));   /* prologue header */
    PUT(heap_listp+DSIZE, PACK(OVERHEAD, 1));   /* prologue footer */
    PUT(heap_listp+WSIZE+DSIZE, PACK(0, 1));    /* epilogue header */
    heap_listp += DSIZE;

    /* Extend the empty heap with a free block of CHUNKSIZE bytes */
    if (extend_heap(CHUNKSIZE/WSIZE) == NULL)
        return -1;
    return 0;
}

void mm_free(void *bp)
{
    size_t size = GET_SIZE(HDRP(bp));

    PUT(HDRP(bp), PACK(size, 0));
    PUT(FTRP(bp), PACK(size, 0));
    coalesce(bp);
}

/* for the first-fit */
static void *find_fit(size_t asize)
{
    char *bp = heap_listp;
 
    if (asize <= 0)
        return NULL;

    do
    {
        if (GET_ALLOC(HDRP(bp)) == 0 && GET_SIZE(HDRP(bp)) >= asize)
            return bp;
        bp = NEXT_BLKP(bp);
    } while (GET_SIZE(HDRP(bp)) != 0);

    return NULL;
}

/* for the next-fit */
static char *last_fit;

static void *find_next_fit(size_t asize)
{
    char *bp;

    if (asize <= 0)
        return NULL;
    
    for (bp = last_fit; GET_SIZE(HDRP(bp)) != 0; bp = NEXT_BLKP(bp)) {
        if (!GET_ALLOC(HDRP(bp)) && asize <= GET_SIZE(HDRP(bp)))
        {
            last_fit = bp;
            return bp;
        }
    }
   
    return NULL;
}

/* for the best-fit */
static void *find_best_fit(size_t asize)
{
    char *bp;
    char *best_fit = NULL;
    if (asize <= 0)
        return NULL;

    for (bp = heap_listp; GET_SIZE(HDRP(bp)) != 0; bp = NEXT_BLKP(bp)) {
        if (!GET_ALLOC(HDRP(bp)) && asize <= GET_SIZE(HDRP(bp)))
        {
            if (best_fit == NULL)
                best_fit = bp;
            else if (GET_SIZE(HDRP(bp)) < GET_SIZE(HDRP(best_fit)))
                best_fit = bp;
        }
    }
    
    return best_fit;
}

static void place(void *bp, size_t asize)
{
    size_t left, size;

    size = GET_SIZE(HDRP(bp));
    if (GET_ALLOC(HDRP(bp)) == 1 || asize <= 0 || asize > size)
        return;
    
    left = size - asize;

    if (left >= OVERHEAD+DSIZE)
    {
        PUT(HDRP(bp), PACK(asize, 1)); 
        PUT(FTRP(bp), PACK(asize, 1)); 
        bp = NEXT_BLKP(bp);
        PUT(HDRP(bp), PACK(left, 0)); 
        PUT(FTRP(bp), PACK(left, 0)); 
    }
    else
    {
        PUT(HDRP(bp), PACK(size, 1));
        PUT(FTRP(bp), PACK(size, 1));
    }
}

void *mm_malloc(size_t size)
{
    size_t asize;           /* adjusted block size */
    size_t extendsize;      /* amount to extend heap if no fit */
    char *bp;

    /* Ignore spurious requests */
    if (size <= 0)
        return NULL;

    /* Adjuest block size to include overhead and alignment reqs */
    if (size <= DSIZE)
        asize = DSIZE + OVERHEAD;
    else
        asize = DSIZE * ((size + OVERHEAD + DSIZE - 1) / DSIZE);

    /* search the free list for a fit */
    if ((bp = find_fit(asize)) != NULL)
    {
        place(bp, asize);
        return bp;
    }

    /* No fit found. Get more memory and place the block */
    extendsize = MAX(asize, CHUNKSIZE);
    if ((bp = extend_heap(extendsize/WSIZE)) == NULL)
        return NULL;
    place(bp, asize);
    return bp;
}
